/*
 * Su-yun Information Technology, Co.,Ltd CONFIDENTIAL
 *
 * (C) Copyright Su-yun Information Technology, Co.,Ltd. 2021 All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains the property of
 * Su-yun Information Technology, Co.,Ltd. The intellectual property herein
 * are protected by trade secret, patent law or copyright law. Dissemination
 * of this information or reproduction of this material is strictly forbidden
 * unless prior written permission is obtained from Su-yun Information Technology.
 *
 * https://suyunplat.com/
 *
 */
package com.xtiim.battery.discharge.server;

import com.xtiim.battery.discharge.common.Configuration;
import com.xtiim.battery.discharge.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.*;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServlet;
import java.net.URL;
import java.util.*;

/**
 * Jetty server implementation
 *
 */
@Slf4j
public class JettyServer {
	private static Map<Integer, Server> SERVER_MAP = new HashMap<Integer, Server>();
	private static Set<ServiceInfo> SERVICES = new HashSet<ServiceInfo>();

	/**
	 * Start Jetty server
	 *
	 * @param port
	 * @return
	 */
	public static synchronized int start(int port) {
		if (SERVER_MAP.containsKey(port)) {
			return 0;
		}

        // Setup Threadpool
		String maxThreads = Configuration.get(Constants.WEB_MAX_CONNECTION, Constants.WEB_MAX_CONNECTION_DEFAULT);
        QueuedThreadPool threadPool = new QueuedThreadPool();
        threadPool.setMaxThreads(Integer.valueOf(maxThreads));

		Server server = new Server(threadPool);

		 // === jetty-http.xml ===
		String idleTimeout = Configuration.get(Constants.WEB_IDLE_TIMEOUT, Constants.WEB_IDLE_TIMEOUT_DEFAULT);
        ServerConnector http = new ServerConnector(server, new HttpConnectionFactory());
        http.setPort(port);
        http.setIdleTimeout(Long.valueOf(idleTimeout));
        server.addConnector(http);

		// create servlet context handler
		ServletContextHandler servletContextHandler = new ServletContextHandler();

		// create web app context handler
		WebAppContext webAppContext = new WebAppContext();
		URL resource = JettyServer.class.getResource("/WebContent");
		webAppContext.setResourceBase(resource != null ? resource.toString() : Constants.WEB_RESOURCE_BASE_DEFAULT);
		webAppContext.setContextPath("/");
		// disables Jetty's memory-mapped mode and avoid web file locking
		webAppContext.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");
		webAppContext.addFilter(JettyAPIFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));

		FilterHolder filterHolder = new FilterHolder(CrossOriginFilter.class);
		filterHolder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
		filterHolder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS_HEADER, "POST, GET, PUT, OPTIONS, DELETE");
		webAppContext.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));

		// combines these two contexts into context collection
		ContextHandlerCollection contexts = new ContextHandlerCollection();
		contexts.setHandlers(new Handler[] { webAppContext, servletContextHandler });

		// set the combined contexts into server
		server.setHandler(contexts);

		SERVER_MAP.put(port, server);

		try {
			server.start();
		} catch (Throwable throwable) {
			System.out.println(throwable.getMessage());
			return -1;
		}

		return 0;
	}

	/**
	 * Add servlet into web container
	 *
	 * @param port
	 * @param path
	 * @param servlet
	 * @return
	 */
	public static synchronized int addServlet(int port, String path, HttpServlet servlet) {
		try {
			if (!SERVER_MAP.containsKey(port)) {
				throw new IllegalArgumentException("There is no Jetty server on port " + port);
			}

			ServiceInfo info = new ServiceInfo(port, path);

			if (SERVICES.contains(info)) {
				if (removeServlet(port, path, servlet) != 0) {
					return -1;
				}
			}
			ServletHolder servletHolder = new ServletHolder(servlet);
			(getServletContextHandler(port)).addServlet(servletHolder, path);
			SERVICES.add(info);

		} catch (Throwable throwable) {
			throwable.printStackTrace();
			return -1;
		}
		return 0;
	}

	/**
	 * Remove servlet from web container
	 *
	 * @param port
	 * @param path
	 * @param servlet
	 * @return
	 */
	public static synchronized int removeServlet(int port, String path, HttpServlet servlet) {
		try {
			ServiceInfo info = new ServiceInfo(port, path);
			if (SERVICES.contains(info)) {
				ServletContextHandler servletContextHandler = getServletContextHandler(port);
				ServletHandler servletHandler = servletContextHandler.getServletHandler();

				List<ServletHolder> servlets = new ArrayList<ServletHolder>();

				Set<String> names = new HashSet<String>();

				for (ServletHolder holder : servletHandler.getServlets()) {
					if (servlet.getClass().isInstance(holder.getServlet())) {
						names.add(holder.getName());
					} else {
						servlets.add(holder);
					}
				}

				List<ServletMapping> mappings = new ArrayList<ServletMapping>();

				for (ServletMapping mapping : servletHandler.getServletMappings()) {
					if (!names.contains(mapping.getServletName())) {
						mappings.add(mapping);
					}
				}

				servletHandler.setServletMappings(mappings.toArray(new ServletMapping[0]));
				servletHandler.setServlets(servlets.toArray(new ServletHolder[0]));

				SERVICES.remove(info);
			}
		} catch (Throwable throwable) {
			throwable.printStackTrace();
			return -1;
		}
		return 0;
	}

	/**
	 * Get servlet context handler
	 *
	 * @param port
	 * @return
	 */
	private static ServletContextHandler getServletContextHandler(int port) {
		Server server = SERVER_MAP.get(port);
		if (server == null) {
			throw new IllegalArgumentException("There is no Jetty server on port " + port);
		}
		ContextHandlerCollection contextHandler = (ContextHandlerCollection) server.getHandler();
		for (Handler handler : contextHandler.getHandlers()) {
			if (handler instanceof ServletContextHandler) {
				return (ServletContextHandler) handler;
			}
		}

		throw new IllegalStateException("There is no ServletContextHandler in Jetty server.");
	}
}
